1 module hip.game2d.ninepatch; 2 public import hip.api.renderer.texture; 3 public import hip.game2d.sprite; 4 import hip.api.renderer.shaders.spritebatch; 5 import hip.api.data.textureatlas; 6 7 enum NinePatchType 8 { 9 SCALED, 10 TILED //I Think this effect is quite ugly, but maybe it'll be useful at some time 11 } 12 13 class NinePatch 14 { 15 uint width, height; 16 float x = 0, y = 0; 17 float scaleX = 1, scaleY = 1; 18 HipSprite[9] sprites; 19 protected HipSpriteVertex[9*4] vertices; 20 IHipTexture texture; 21 NinePatchType stretchStrategy; 22 23 this(IHipTexture tex, NinePatchType t = NinePatchType.SCALED) 24 { 25 texture = tex; 26 foreach(i; 0..9) 27 { 28 sprites[i] = new HipSprite(tex); 29 sprites[i].setOrigin(0,0); 30 } 31 stretchStrategy = t; 32 } 33 34 this(uint width, uint height, IHipTexture tex, NinePatchType type = NinePatchType.SCALED) 35 { 36 this.width = width; 37 this.height = height; 38 texture = tex; 39 stretchStrategy = type; 40 for(int i = 0; i < 9; i++) 41 { 42 sprites[i] = new HipSprite(tex); 43 sprites[i].setOrigin(0,0); 44 } 45 46 setTextureRegions(); 47 setSize(width, height); 48 } 49 50 void setSize(int width, int height) 51 { 52 this.width = width; 53 this.height = height; 54 build(); 55 } 56 57 /** 58 * The arguments will be divided by the texture width and height for 59 * generating the UVs 60 */ 61 void setTextureRegions(uint x, uint y, uint width, uint height) 62 { 63 int texWidth = sprites[0].getTextureWidth; 64 int texHeight = sprites[0].getTextureHeight; 65 66 float tx = cast(float)x/texWidth; 67 float ty = cast(float)y/texHeight; 68 69 float xw = (cast(float)width/3.0f)/texWidth; 70 float yh = (cast(float)height/3.0f)/texHeight; 71 float xw2 = xw*2; 72 float xw3 = xw*3; 73 float yh2 = yh*2; 74 float yh3 = yh*3; 75 76 sprites[TOP_LEFT].setRegion (tx+0, ty+0, tx+xw, ty+yh); 77 sprites[TOP_MID].setRegion (tx+xw, ty+0, tx+xw2, ty+yh); 78 sprites[TOP_RIGHT].setRegion(tx+xw2, ty+0, tx+xw3, ty+yh); 79 80 sprites[MID_LEFT].setRegion (tx+0, ty+yh, tx+xw, ty+yh2); 81 sprites[MID_MID].setRegion (tx+xw, ty+ yh,tx+ xw2,ty+ yh2); 82 sprites[MID_RIGHT].setRegion(tx+xw2,ty+ yh,tx+ xw3,ty+ yh2); 83 84 sprites[BOT_LEFT].setRegion (tx+0, ty+yh2, tx+xw, ty+yh3); 85 sprites[BOT_MID].setRegion (tx+xw, ty+ yh2,tx+ xw2,ty+ yh3); 86 sprites[BOT_RIGHT].setRegion(tx+xw2,ty+ yh2,tx+ xw3,ty+ yh3); 87 } 88 89 /** 90 * Cuts the entire image in 9 slices 91 */ 92 void setTextureRegions() 93 { 94 setTextureRegions(0, 0, sprites[0].getTextureWidth, sprites[0].getTextureHeight); 95 } 96 97 98 void build() 99 { 100 static float getXScalingFactor(ubyte index, HipSprite[] sprites, uint width) 101 { 102 float ret = (width - (sprites[index-1].width + sprites[index+1].width)); 103 if(sprites[index].width != 0) 104 ret/= cast(float)sprites[index].width; 105 return ret; 106 } 107 108 static float getYScalingFactor(ubyte index, HipSprite[] sprites, uint height) 109 { 110 float ret = (height - (sprites[index-3].height + sprites[index+3].height)); 111 if(sprites[index].height != 0) 112 ret/= cast(float)sprites[index].height; 113 return ret; 114 } 115 /** 116 * Receives the _LEFT part to calculate the scale. 117 * This scale is used whenever there is a need of scale being less than 1 118 * Params: 119 * index = 120 * sprites = 121 * width = 122 * Returns: 123 */ 124 static float getSingleXScaling(ubyte index, HipSprite[] sprites, uint width) 125 { 126 if(width < sprites[index].width + sprites[index+2].width) 127 return cast(float)width / (sprites[index].width + sprites[index+2].width); 128 return 1; 129 } 130 static float getSingleYScaling(ubyte index, HipSprite[] sprites, uint height) 131 { 132 if(height < sprites[index].height + sprites[index+6].height) 133 return cast(float)height / (sprites[index].height + sprites[index+6].height); 134 return 1; 135 } 136 137 float scaleXTop = getSingleXScaling(TOP_LEFT, sprites, width); 138 float scaleYTop = getSingleYScaling(TOP_LEFT, sprites, height); 139 140 141 int spWidth = sprites[TOP_LEFT].width; 142 int spHeight = sprites[TOP_LEFT].height; 143 144 int px2 = cast(int)(width-spWidth*scaleXTop); 145 int py2 = cast(int)(height-spHeight*scaleYTop); 146 147 148 //First, take care of those which don't scale. 149 sprites[TOP_LEFT].setPosition(x, y); 150 sprites[TOP_RIGHT].setPosition(x + px2, y); 151 sprites[BOT_LEFT].setPosition(x, y + py2); 152 sprites[BOT_RIGHT].setPosition(x + px2, y + py2); 153 154 ///Those scales may only change whenever they are actually smaller than a scalable size 155 sprites[TOP_LEFT].setScale(scaleXTop, scaleYTop); 156 sprites[TOP_RIGHT].setScale(scaleXTop, scaleYTop); 157 sprites[BOT_LEFT].setScale(scaleXTop, scaleYTop); 158 sprites[BOT_RIGHT].setScale(scaleXTop, scaleYTop); 159 160 //Now, those which scales in only one direction 161 sprites[TOP_MID].setPosition(x+spWidth, y); 162 sprites[MID_LEFT].setPosition(x, y+spHeight); 163 sprites[MID_RIGHT].setPosition(x+ px2, y+spHeight); 164 sprites[BOT_MID].setPosition(x+spWidth, y + py2); 165 sprites[MID_MID].setPosition(spWidth+x, spHeight+y); 166 167 168 169 if(stretchStrategy == NinePatchType.SCALED) 170 { 171 sprites[TOP_MID].setScale(getXScalingFactor(TOP_MID ,sprites, width), scaleYTop); 172 sprites[MID_LEFT].setScale(scaleXTop, getYScalingFactor(MID_LEFT, sprites, height)); 173 sprites[MID_RIGHT].setScale(scaleXTop, getYScalingFactor(MID_RIGHT, sprites, height)); 174 sprites[BOT_MID].setScale(getXScalingFactor(BOT_MID, sprites, width), scaleYTop); 175 176 //The last one 177 sprites[MID_MID].setScale(getXScalingFactor(MID_MID, sprites, width), getYScalingFactor(MID_MID, sprites, height)); 178 } 179 else 180 { 181 sprites[TOP_MID].setTiling(getXScalingFactor(TOP_MID ,sprites, width), scaleYTop); 182 sprites[MID_LEFT].setTiling(scaleXTop, getYScalingFactor(MID_LEFT, sprites, height)); 183 sprites[MID_RIGHT].setTiling(scaleXTop, getYScalingFactor(MID_RIGHT, sprites, height)); 184 sprites[BOT_MID].setTiling(getXScalingFactor(BOT_MID, sprites, width), scaleYTop); 185 186 //The last one 187 sprites[MID_MID].setTiling(getXScalingFactor(MID_MID, sprites, width), getYScalingFactor(MID_MID, sprites, height)); 188 } 189 190 // uint thresholdWidth = spWidth*2; 191 // uint thresholdHeight = spHeight*2; 192 193 // if(width < thresholdWidth) 194 // { 195 // float sX = (width/2.0)/thresholdWidth; 196 // sprites[TOP_LEFT].setScale(sX, sprites[TOP_LEFT].scaleY); 197 // sprites[TOP_RIGHT].setScale(sX, sprites[TOP_RIGHT].scaleY); 198 199 // sprites[MID_LEFT].setScale(sX, sprites[MID_LEFT].scaleY); 200 // sprites[MID_RIGHT].setScale(sX, sprites[MID_RIGHT].scaleY); 201 202 // sprites[BOT_LEFT].setScale(sX, sprites[BOT_LEFT].scaleY); 203 // sprites[BOT_RIGHT].setScale(sX, sprites[BOT_RIGHT].scaleY); 204 205 // sprites[TOP_MID].setScale(0,0); 206 // sprites[MID_MID].setScale(0,0); 207 // sprites[BOT_MID].setScale(0,0); 208 // } 209 // if(height < thresholdHeight) 210 // { 211 // float sY = (height/2.0)/thresholdHeight; 212 // sprites[TOP_LEFT].setScale(sprites[TOP_LEFT].scaleX, sY); 213 // sprites[TOP_RIGHT].setScale(sprites[TOP_RIGHT].scaleX, sY); 214 215 // sprites[MID_LEFT].setScale(sprites[MID_LEFT].scaleX, sY); 216 // sprites[MID_RIGHT].setScale(sprites[MID_RIGHT].scaleX, sY); 217 218 // sprites[BOT_LEFT].setScale(sprites[BOT_LEFT].scaleX, sY); 219 // sprites[BOT_RIGHT].setScale(sprites[BOT_RIGHT].scaleX, sY); 220 221 // sprites[TOP_MID].setScale(0,0); 222 // sprites[MID_MID].setScale(0,0); 223 // sprites[BOT_MID].setScale(0,0); 224 // } 225 226 vertices[0..4] = sprites[TOP_LEFT].getVertices(); 227 vertices[4..8] = sprites[TOP_MID].getVertices(); 228 vertices[8..12] = sprites[TOP_RIGHT].getVertices(); 229 vertices[12..16] = sprites[MID_LEFT].getVertices(); 230 vertices[16..20] = sprites[MID_MID].getVertices(); 231 vertices[20..24] = sprites[MID_RIGHT].getVertices(); 232 vertices[24..28] = sprites[BOT_LEFT].getVertices(); 233 vertices[28..32] = sprites[BOT_MID].getVertices(); 234 vertices[32..36] = sprites[BOT_RIGHT].getVertices(); 235 } 236 237 void setTopLeft(TextureCoordinatesQuad q){sprites[TOP_LEFT].setRegion(q);} 238 void setTopMid(TextureCoordinatesQuad q){sprites[TOP_MID].setRegion(q);} 239 void setTopRight(TextureCoordinatesQuad q){sprites[TOP_RIGHT].setRegion(q);} 240 241 242 void setMidLeft (TextureCoordinatesQuad q){sprites[MID_LEFT].setRegion(q);} 243 void setMidMid (TextureCoordinatesQuad q){sprites[MID_MID].setRegion(q);} 244 void setMidRight(TextureCoordinatesQuad q){sprites[MID_RIGHT].setRegion(q);} 245 246 void setBotLeft (TextureCoordinatesQuad q){sprites[BOT_LEFT].setRegion(q);} 247 void setBotMid (TextureCoordinatesQuad q){sprites[BOT_MID].setRegion(q);} 248 void setBotRight(TextureCoordinatesQuad q){sprites[BOT_RIGHT].setRegion(q);} 249 250 251 void setPosition(float x, float y) 252 { 253 this.x = x; 254 this.y = y; 255 updatePosition(); 256 } 257 258 259 void setColor(HipColor color) 260 { 261 int quad = 0; 262 for(int i = 0; i < 9; i++) 263 { 264 quad = cast(int)(i*4); 265 vertices[quad].vColor = color; 266 vertices[quad+1].vColor = color; 267 vertices[quad+2].vColor = color; 268 vertices[quad+3].vColor = color; 269 } 270 } 271 public ref HipSpriteVertex[4*9] getVertices(){return vertices;} 272 273 274 /** 275 * Use this function instead of build for less overhead 276 */ 277 protected void updatePosition() 278 { 279 uint spWidth = sprites[TOP_LEFT].width; 280 uint spHeight = sprites[TOP_LEFT].height; 281 sprites[TOP_LEFT].setPosition(x, y); 282 sprites[TOP_RIGHT].setPosition(x + (width-spWidth), y); 283 sprites[BOT_LEFT].setPosition(x, y + (height-spHeight)); 284 sprites[BOT_RIGHT].setPosition(x + (width-spWidth), y + (height-spHeight)); 285 sprites[TOP_MID].setPosition(x+spWidth, y); 286 sprites[MID_LEFT].setPosition(x, y+spHeight); 287 sprites[MID_RIGHT].setPosition(x+width-spWidth, y+spHeight); 288 sprites[BOT_MID].setPosition(x+spWidth, y + (height-spHeight)); 289 sprites[MID_MID].setPosition(spWidth+x, spHeight+y); 290 291 for(uint i = 0; i < 9; i++) 292 { 293 uint quad = i*4; 294 HipSpriteVertex[] verts = sprites[i].getVertices(); 295 vertices[quad].vPosition = verts[0].vPosition; 296 vertices[quad+1].vPosition = verts[1].vPosition; 297 vertices[quad+2].vPosition = verts[2].vPosition; 298 vertices[quad+3].vPosition = verts[3].vPosition; 299 } 300 } 301 302 void draw() 303 { 304 foreach(HipSprite sp; sprites) 305 sp.draw; 306 } 307 /** 308 Creates a NinePatch from the matrix, starting from left to right, top to bottom 309 * Params: 310 * rects = The rects that defines the sprite regions 311 * tex = The texture being used as a reference to the regions 312 * width = The width of the resulting nine patch 313 * height = The height of the resulting nine patch 314 * t = The scaling type 315 * Returns: 316 */ 317 static NinePatch fromQuads(AtlasRect[9] rects, IHipTexture tex, int width, int height, NinePatchType t = NinePatchType.SCALED) 318 { 319 NinePatch ret = new NinePatch(tex, t); 320 321 AtlasSize sz = AtlasSize(tex.getWidth, tex.getHeight); 322 foreach(i, sp; ret.sprites) 323 { 324 // rects[i].y = sz.height - rects[i].y; 325 sp.width = cast(uint)rects[i].width; 326 sp.height = cast(uint)rects[i].height; 327 sp.setRegion(rects[i].toQuad(sz)); 328 } 329 330 ret.setSize(width, height); 331 return ret; 332 } 333 334 335 } 336 337 private enum : ubyte 338 { 339 TOP_LEFT = 0, 340 TOP_MID, 341 TOP_RIGHT, 342 343 MID_LEFT, 344 MID_MID, 345 MID_RIGHT, 346 347 BOT_LEFT, 348 BOT_MID, 349 BOT_RIGHT 350 }